﻿using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace CavinatorService.DAO.Base
{
    /// <summary>
    /// Classe responsável pelo acesso a dados
    /// </summary>
    public class DBAccess
    {
        #region Atributos

        /// <summary>
        /// Atributo responsável pela string de conexão com o banco de dados
        /// </summary>
        private readonly string CONEXAO_BANCO;

        /// <summary>
        /// Atributo responsável pelo provider do banco de dados
        /// </summary>
        private readonly string PROVIDER;

        /// <summary>
        /// Atributo responsável pelo command
        /// </summary>
        private IDbCommand tipoDbCommand;

        /// <summary>
        /// Atributo responsável pelo command
        /// </summary>
        public IDbConnection Conexao { get; protected set; }

        /// <summary>
        /// Atributo responsável pelo command
        /// </summary>
        private IDbTransaction transacao = null;

        public IDbTransaction Transacao
        {
            get { return transacao; }
        }
        #endregion

        #region Construtores

        /// <summary>
        /// Construtor padrão
        /// </summary>
        public DBAccess()
        {

        }

        /// <summary>
        /// Construtor recebendo uma string de conexão
        /// </summary>
        /// <param name="conexaoBanco">String de conexão com o banco</param>
        public DBAccess(string conexaoBanco)
        {
            PROVIDER = ConfigurationManager.ConnectionStrings[conexaoBanco].ProviderName;
            CONEXAO_BANCO = ConfigurationManager.ConnectionStrings[conexaoBanco].ConnectionString;
            AtribuirBanco();
        }

        #endregion

        #region Conexão

        /// <summary>
        /// Conectar ao banco de dados
        /// </summary>
        /// <returns><see cref="SqlConnection"/> já conectada</returns>
        public IDbConnection Conectar()
        {
            Conexao = AtribuirBanco();
            Conexao.Open();
            return Conexao;
        }

        /// <summary>
        /// Faz a atribuição pelo tipo do banco
        /// </summary>
        /// <returns>IdbConnection</returns>
        private IDbConnection AtribuirBanco()
        {
            IDbConnection conexao = null;
            switch (PROVIDER)
            {
                case "System.Data.SqlClient":
                    conexao = new SqlConnection(CONEXAO_BANCO);
                    tipoDbCommand = new SqlCommand();
                    break;
            }
            return conexao;
        }


        /// <summary>
        /// Desconectar do banco de dados
        /// </summary>
        /// <param name="conexao"><see cref="SqlConnection"/> a ser desconectada</param>
        public void Desconectar()
        {
            Conexao.Close();
            Conexao.Dispose();
            
          
            Conexao = null;
        }

        #endregion

        #region Transação

        /// <summary>
        /// Inicia uma transação
        /// </summary>
        /// <param name="conexao"><see cref="SqlConnection"/> que dará origem a transação</param>
        /// <returns><see cref="SqlTransaction"/> já iniciada</returns>
        public void IniciarTransacao()
        {
            if (Conexao == null)
                Conexao = AtribuirBanco();

            if (Conexao.State != ConnectionState.Open)
                Conexao.Open();

            transacao = Conexao.BeginTransaction();
        }

        /// <summary>
        /// Salvar uma transação
        /// </summary>
        /// <param name="transacao"><see cref="SqlTransaction"/> a ser salva</param>
        public void SalvarTransacao()
        {
            if (transacao != null && transacao.Connection != null)
                transacao.Commit();
            Conexao.Close();

            //
            transacao = null;
            Conexao = null;
        }

        /// <summary>
        /// Cancelar uma transação
        /// </summary>
        /// <param name="transacao"><see cref="SqlTransaction"/> a ser cancelada</param>
        public void CancelarTransacao()
        {
            if (transacao != null && transacao.Connection != null)
                transacao.Rollback();
            Conexao.Close();

            //
            transacao = null;
            Conexao = null;
        }

        #endregion

        #region Métodos Protegidos

        /// <summary>
        /// Criar um comando
        /// </summary>
        /// <param name="tipoComando">Tipo do comando</param>
        /// <param name="comando">String com o comando</param>
        /// <param name="parametros">Lista com os parametros</param>
        /// <param name="conexao">Conexão com o banco</param>
        /// <param name="transacao">Transação a ser utilizada</param>
        /// <returns>Retorna um comando</returns>
        protected IDbCommand CriarComando(CommandType tipoComando, string comando, IList<IDbDataParameter> parametros = null)
        {
            if (transacao == null)
                AtribuirBanco();

            //Declarando o comando que será retornado
            IDbCommand scComando = tipoDbCommand;

            //Verificando se não veio null nos parametros
            if (parametros != null)
                AdicionarParametros(scComando, parametros);

            //Atribuindo uma conexão existente ou criando uma nova e atribuindo uma transação caso tenha sido passado
            AtribuirConexao(ref scComando);

            scComando.CommandText = comando;

            scComando.CommandType = tipoComando;

            scComando.CommandTimeout = 0;

            return scComando;
        }


        /// <summary>
        /// Retorna os dados
        /// </summary>
        /// <param name="tipoComando">Tipo do comando</param>
        /// <param name="comando">String com o comando</param>
        /// <param name="parametros">Lista com os parametros</param>
        /// <param name="conexao">Conexão com o banco</param>
        /// <param name="transacao">Transação a ser utilizada</param>
        /// <returns>Retorna um DataTable com o resultado</returns>
        protected DataTable RetornarDados(CommandType tipoComando, string comando, IList<IDbDataParameter> parametros = null)
        {
            DataTable dtRetorno = new DataTable("Retorno");

            using (IDataReader sqlReader = RetornarReader(tipoComando, comando, parametros, CommandBehavior.CloseConnection))
                dtRetorno.Load(sqlReader);

            foreach (DataColumn dcColumn in dtRetorno.Columns)
                dcColumn.ReadOnly = false;

            return dtRetorno;
        }

        /// <summary>
        /// Retorna um DataReader
        /// </summary>
        /// <param name="tipoComando">Tipo do comando</param>
        /// <param name="comando">String com o comando</param>
        /// <param name="parametros">Lista com os parametros</param>
        /// <param name="comportamento">Comportamento do DataReader</param>
        /// <param name="conexao">Conexão com o banco</param>
        /// <param name="transacao">Transação a ser utilizada</param>
        /// <returns>DataReader da consulta</returns>
        protected IDataReader RetornarReader(CommandType tipoComando, string comando, IList<IDbDataParameter> parametros = null, CommandBehavior comportamento = CommandBehavior.CloseConnection)
        {
            using (IDbCommand scComando = CriarComando(tipoComando, comando, parametros))
            {
                IDataReader retorno = scComando.ExecuteReader(comportamento);

                parametros = AtualizaParametros(scComando);

                return retorno;
            }
        }

        /// <summary>
        /// Retorna as linhas afetadas por um comando
        /// </summary>
        /// <param name="tipoComando">Tipo do comando</param>
        /// <param name="comando">String com o comando</param>
        /// <param name="parametros">Lista com os parametros</param>
        /// <param name="conexao">Conexão com o banco</param>
        /// <param name="transacao">Transação a ser utilizada</param>
        /// <returns>Número de linhas afetadas pelo comando</returns>
        protected int RetornarAfetadas(CommandType tipoComando, string comando, IList<IDbDataParameter> parametros = null)
        {
            using (IDbCommand scComando = CriarComando(tipoComando, comando, parametros))
            {
                int retorno = scComando.ExecuteNonQuery();

                parametros = AtualizaParametros(scComando);

                if (transacao == null)
                scComando.Connection.Close();

                return retorno;
            }
        }

        /// <summary>
        /// Retorna a primeira coluna da primeira linha
        /// </summary>
        /// <param name="tipoComando">Tipo do comando</param>
        /// <param name="comando">String com o comando</param>
        /// <param name="parametros">Lista com os parametros</param>
        /// <param name="conexao">Conexão com o banco</param>
        /// <param name="transacao">Transação a ser utilizada</param>
        /// <returns>Retorna a primeira coluna da primeira linha</returns>
        protected object RetornarUnicoValor(CommandType tipoComando, string comando, IList<IDbDataParameter> parametros = null)
        {
            using (IDbCommand scComando = CriarComando(tipoComando, comando, parametros))
            {
                object retorno = scComando.ExecuteScalar();

                parametros = AtualizaParametros(scComando);

                if(transacao == null)
                scComando.Connection.Close();

                return retorno;
            }
        }

        #endregion

        #region Métodos Privados

        /// <summary>
        /// Atualiza os parametros passados de acordo com o comando
        /// </summary>
        /// <param name="comando">Comando com os parametros a serem copiados</param>
        /// <returns>Lista com os parametros atualizados</returns>
        private IList<IDbDataParameter> AtualizaParametros(IDbCommand comando)
        {
            IList<IDbDataParameter> retorno = new List<IDbDataParameter>();

            for (int i = 0; i < comando.Parameters.Count; i++)
                retorno.Add((IDbDataParameter)comando.Parameters[i]);

            return retorno;
        }

        /// <summary>
        /// Atribui uma conexão e uma transação caso essa seja passada a um comando
        /// </summary>
        /// <param name="comando">Comando a ser utilizado</param>
        /// <param name="conexao">Conexão a ser utilizada</param>
        /// <param name="transacao">Transação a ser utilizada</param>
        private void AtribuirConexao(ref IDbCommand comando)
        {
            //Verificando se foi passado uma transação como parametro
            if (transacao != null)
            {
                comando.Transaction = transacao;
                comando.Connection = transacao.Connection;

            }
            else
            {
                comando.Connection = Conectar();
            }
        }

        /// <summary>
        /// Adicionar os parametros a um comando
        /// </summary>
        /// <param name="comando">Comando a ser adicionado os parametros</param>
        /// <param name="parametros">Parametros a serem adicionados</param>
        private void AdicionarParametros(IDbCommand comando, IList<IDbDataParameter> parametros)
        {
            comando.Parameters.Clear();

            //Para cada parametro na lista, adicionar ao comando
            for (int i = 0; i < parametros.Count; i++)
                comando.Parameters.Add(parametros[i]);
        }

        #endregion
    }
}
